/***************************************************************************
	File                 : MatrixModel.cpp
	Project              : QtiPlot
--------------------------------------------------------------------
	Copyright            : (C) 2007 - 2012 by Ion Vasilief
	Email (use @ for *)  : ion_vasilief*yahoo.fr
	Description          : QtiPlot's matrix model

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *  This program is free software; you can redistribute it and/or modify   *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation; either version 2 of the License, or      *
 *  (at your option) any later version.                                    *
 *                                                                         *
 *  This program is distributed in the hope that it will be useful,        *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 *  GNU General Public License for more details.                           *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
 *   Boston, MA  02110-1301  USA                                           *
 *                                                                         *
 ***************************************************************************/
#include <QtGui>
#include <QFile>
#include <QTextStream>

#include <Matrix.h>
#include <MatrixModel.h>
#include <MatrixCommand.h>
#include <muParserScript.h>
#include <ScriptingEnv.h>
#include <fft2D.h>

#include <gsl/gsl_linalg.h>
#include <gsl/gsl_errno.h>

#include <qwt_color_map.h>
#include <stdlib.h>

#ifdef HAVE_ALGLIB
	#include <spline2d.h>
#endif

MatrixModel::MatrixModel(int rows, int cols, QObject *parent)
	: QAbstractTableModel(parent),
	 d_matrix((Matrix*)parent)
{
	init();

	if (d_matrix){
		d_txt_format = d_matrix->textFormat().toAscii();
		d_num_precision = d_matrix->precision();
		d_locale = d_matrix->locale();
	}

	if (canResize(rows, cols)){
		d_rows = rows;
		d_cols = cols;
		int cell = 0;
		int size = rows*cols;
		for (int i = 0; i < size; i++)
			d_data[cell++] = NAN;
	}
}

MatrixModel::MatrixModel(const QImage& image, QObject *parent)
     : QAbstractTableModel(parent),
	 d_matrix((Matrix*)parent)
{
	init();
	setImage(image);
}

void MatrixModel::init()
{
	d_calculated_values = false;
	d_txt_format = 'g';
	d_num_precision = 6;
	d_locale = QLocale();
	d_direct_matrix = NULL;
	d_inv_matrix = NULL;
	d_inv_perm = NULL;

	d_rows = 1;
	d_cols = 1;
	d_data_block_size = QSize(1, 1);
	d_data = (double *)malloc(sizeof(double));
}

void MatrixModel::setImage(const QImage& image)
{
	if (!canResize(image.height(), image.width()))
		return;
	
	d_rows = image.height();
	d_cols = image.width();
	int cell = 0;
	for (int i = 0; i < d_rows; i++){
		for (int j = 0; j < d_cols; j++)
			d_data[cell++] = qGray(image.pixel(j, i));
	}

	d_calculated_values = false;
}

Qt::ItemFlags MatrixModel::flags(const QModelIndex & index ) const
{
	if (index.isValid())
		return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
	else
		return Qt::ItemIsEnabled;
}

int MatrixModel::rowCount(const QModelIndex & /* parent */) const
{
	return d_rows;
}

int MatrixModel::columnCount(const QModelIndex & /* parent */) const
{
	return d_cols;
}

void MatrixModel::setRowCount(int rows)
{
	if (d_rows == rows)
		return;

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

	if (rows > d_rows )
		insertRows(d_rows, rows - d_rows);
	else if (rows < d_rows )
		removeRows(rows, d_rows - rows);

	QApplication::restoreOverrideCursor();
}

void MatrixModel::setColumnCount(int cols)
{
	if (d_cols == cols)
		return;

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

	if (cols > d_cols )
		insertColumns(d_cols, cols - d_cols);
	else if (cols < d_cols )
		removeColumns(cols, d_cols - cols);

	QApplication::restoreOverrideCursor();
}

void MatrixModel::setDimensions(int rows, int cols)
{
	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

	if (rows < d_rows){//first remove rows (faster)
		removeRows(rows, d_rows - rows);
		setColumnCount(cols);
	} else {
		setColumnCount(cols);
		if (rows > d_rows )
			insertRows(d_rows, rows - d_rows);
	}

	QApplication::restoreOverrideCursor();
}

double MatrixModel::cell(int row, int col)
{
	int i = d_cols*row + col;
	if (i < 0 || i >= d_rows*d_cols)
		return NAN;

	return d_data[i];
}

void MatrixModel::setCell(int row, int col, double val)
{
	int i = d_cols*row + col;
	if (i < 0 || i >= d_rows*d_cols)
		return;

	d_data[i] = val;
}

QString MatrixModel::text(int row, int col)
{
	int i = d_cols*row + col;
	double val = d_data[i];
	if (i < 0 || i >= d_rows*d_cols || isnan(val))
		return "";

	if (d_matrix){
		QLocale locale = d_matrix->locale();
		return locale.toString(val, d_matrix->textFormat().toAscii(), d_matrix->precision());
	}
	return d_locale.toString(val, d_txt_format, d_num_precision);
}

void MatrixModel::setText(int row, int col, const QString& text)
{
	int i = d_cols*row + col;
	if (i < 0 || i>= d_rows*d_cols)
		return;

	if (text.isEmpty())
		d_data[i] = NAN;
	else {
		if (d_matrix)
			d_data[i] = d_matrix->locale().toDouble(text);
		else
			d_data[i] = d_locale.toDouble(text);
	}
}

double MatrixModel::data(int row, int col) const
{
	int i = d_cols*row + col;
	if (i < 0 || i>= d_rows*d_cols)
		return 0.0;

	return d_data[i];
}

double MatrixModel::x(int col) const
{
	if (col < 0 || col >= d_cols)
		return 0.0;

	double start = d_matrix->xStart();
	double end = d_matrix->xEnd();
	if (start < end)
		return start + col*d_matrix->dx();
	else
		return start - col*d_matrix->dx();

	return 0.0;
}

double MatrixModel::y(int row) const
{
	if (row < 0 || row >= d_rows)
		return 0.0;

	double start = d_matrix->yStart();
	double end = d_matrix->yEnd();
	if (start < end)
		return start + row*d_matrix->dy();
	else
		return start - row*d_matrix->dy();

	return 0.0;
}

QVariant MatrixModel::headerData ( int section, Qt::Orientation orientation, int role) const
{
	if (!d_matrix || d_matrix->headerViewType() == Matrix::ColumnRow)
		return QAbstractItemModel::headerData(section, orientation, role);

	QLocale locale = d_locale;
	int prec = d_num_precision;
	char fmt = d_txt_format;
	if (d_matrix){
		locale = d_matrix->locale();
		fmt = d_matrix->textFormat().toAscii();
		prec = d_matrix->precision();
	}

	if (role == Qt::DisplayRole || role == Qt::EditRole){
		if (orientation == Qt::Horizontal){
			double start = d_matrix->xStart();
			double end = d_matrix->xEnd();
			double dx = d_matrix->dx();
			if (start < end)
				return QVariant(locale.toString(start + section*dx, fmt, prec));
			else
				return QVariant(locale.toString(start - section*dx, fmt, prec));
		} else if (orientation == Qt::Vertical){
			double start = d_matrix->yStart();
			double end = d_matrix->yEnd();
			double dy = d_matrix->dy();
			if (start < end)
				return QVariant(locale.toString(start + section*dy, fmt, prec));
			else
				return QVariant(locale.toString(start - section*dy, fmt, prec));
		}
	}
	return QAbstractItemModel::headerData(section, orientation, role);
}

QVariant MatrixModel::data(const QModelIndex &index, int role) const
{
	if (!index.isValid())
		return QVariant();

	int i = d_cols*index.row() + index.column();
	double val = d_data[i];
	if (isnan (val))
		return QVariant();

	if (role == Qt::DisplayRole || role == Qt::EditRole){
		if (d_matrix)
			return QVariant(d_matrix->locale().toString(val, d_matrix->textFormat().toAscii(), d_matrix->precision()));
		else
			return QVariant(d_locale.toString(val, d_txt_format, d_num_precision));
	} else
		return QVariant();
}

bool MatrixModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
	if (!index.isValid())
		return false;

	int i = d_cols*index.row() + index.column();
	double valBefore = d_data[i];
	if(role == Qt::EditRole){
		if (value.toString().isEmpty())
			d_data[i] = NAN;
		else {
			double val = NAN;
			if (d_matrix)
				val = d_matrix->locale().toDouble(value.toString());
			else
				val = d_locale.toDouble(value.toString());

			if (val != valBefore)
				d_data[i] = val;
			else
				return false;
		}
	}

	if(index.row() + 1 >= d_rows){
		insertRows(d_rows, 1);
		d_matrix->resetView();
	}

	d_matrix->undoStack()->push(new MatrixEditCellCommand(this, index, valBefore, d_data[i],
								tr("Edited cell") + " (" + QString::number(index.row() + 1) + "," +
								QString::number(index.column() + 1) + ")"));
	d_matrix->notifyChanges();
	d_matrix->notifyModifiedData();
	d_matrix->moveCell(index);
	return false;
}

bool MatrixModel::canResize(int rows, int cols)
{
	if (rows <= 0 || cols <= 0 || INT_MAX/rows < cols){ //avoid integer overflow
		QApplication::restoreOverrideCursor();
		QMessageBox::critical(d_matrix, tr("QtiPlot") + " - " + tr("Input Size Error"),
		tr("The dimensions you have specified are not acceptable!") + "\n" +
		tr("Please enter positive values for which the product rows*columns does not exceed the maximum integer value available on your system!"));
		return false;
	}

	if (d_data_block_size.width()*d_data_block_size.height() >= rows*cols)
		return true;

	double *new_data = (double *)realloc(d_data, rows*cols*sizeof(double));
	if (new_data){
		d_data = new_data;
		d_data_block_size = QSize(rows, cols);
		return true;
	}

	QApplication::restoreOverrideCursor();
	QMessageBox::critical(d_matrix, tr("QtiPlot") + " - " + tr("Memory Allocation Error"),
	tr("Not enough memory, operation aborted!"));
	return false;
}

bool MatrixModel::removeColumns(int column, int count, const QModelIndex & parent)
{
	beginRemoveColumns(parent, column, column + count - 1);

	d_cols -= count;
	d_data_block_size = QSize(d_rows, d_cols);

	int size = d_rows*d_cols;
	for (int i = column; i < size; i++){
		int aux = (i - column)/d_cols + 1;
		d_data[i] = d_data[i + aux*count];
	}

	d_data = (double *)realloc (d_data, size*sizeof(double));

	d_calculated_values = false;
	endRemoveColumns();
	return true;
}

bool MatrixModel::insertColumns(int column, int count, const QModelIndex & parent)
{
	if (!canResize(d_rows, d_cols + count))
		return false;

	beginInsertColumns(parent, column, column + count - 1);

	int offset = column + count;
	int oldCell = d_rows*d_cols - 1;
	d_cols += count;
	int cell = d_rows*d_cols - 1;
	for (int i = d_rows - 1; i >= 0; i--){
		for (int j = d_cols - 1; j >= offset; j--)
			d_data[cell--] = d_data[oldCell--];

		for (int j = offset - 1; j >= column; j--)
			d_data[cell--] = NAN;

		for (int j = column - 1; j >= 0; j--)
			d_data[cell--] = d_data[oldCell--];
	}

	d_calculated_values = false;
	endInsertColumns();
	return true;
}

bool MatrixModel::insertRows(int row, int count, const QModelIndex & parent)
{
	if (!canResize(d_rows + count, d_cols))
		return false;

	beginInsertRows(parent, row, row + count - 1);

	int oldSize = d_rows*d_cols;
	d_rows += count;

	int insertedCells = count*d_cols;
	int startCell = row*d_cols;
	for (int i = oldSize - 1; i >= startCell; i--)
		d_data[i + insertedCells] = d_data[i];
	for (int i = 0; i < insertedCells; i++)
		d_data[startCell++] = NAN;

	d_calculated_values = false;
	endInsertRows();
	return true;
}

bool MatrixModel::removeRows(int row, int count, const QModelIndex & parent)
{
	beginRemoveRows(parent, row, row + count - 1);

	d_rows -= count;
	d_data_block_size = QSize(d_rows, d_cols);

	int removedCells = count*d_cols;
	int size = d_rows*d_cols;
	for (int i = row*d_cols; i < size; i++)
		d_data[i] = d_data[i + removedCells];

	d_data = (double *)realloc(d_data, size * sizeof(double));

	d_calculated_values = false;
	endRemoveRows();
	return true;
}

QImage MatrixModel::renderImage()
{
	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

	QImage image(QSize(d_cols, d_rows), QImage::Format_RGB32);
	LinearColorMap color_map = d_matrix->colorMap();

	const QwtDoubleInterval intensityRange = d_matrix->colorRange();
	for ( int i = 0; i < d_rows; i++ ){
		QRgb *line = (QRgb *)image.scanLine(i);
		for ( int j = 0; j < d_cols; j++){
			double val = d_data[i*d_cols + j];
			if (isnan(val))
				*line++ = color_map.rgb(intensityRange, 0.0);
			else if(fabs(val) < HUGE_VAL)
				*line++ = color_map.rgb(intensityRange, val);
		}
	 }
	 QApplication::restoreOverrideCursor();
	 return image;
}

bool MatrixModel::importASCII(const QString &fname, const QString &sep, int ignoredLines,
    bool stripSpaces, bool simplifySpaces, const QString& commentString, int importAs,
	const QLocale& locale, int endLineChar, int maxRows)
{
	int rows = 0;
	QString name = MdiSubWindow::parseAsciiFile(fname, commentString, endLineChar, ignoredLines, maxRows, rows);
	if (name.isEmpty())
		return false;
	QFile f(name);
	if (!f.open(QIODevice::ReadOnly))
		return false;

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

    QTextStream t(&f);
	QLocale l = d_locale;
	if (d_matrix)
		l = d_matrix->locale();
	bool updateDecimalSeparators = (l != locale) ? true : false;

	QString s = t.readLine();
	if (simplifySpaces)
		s = s.simplifyWhiteSpace();
	else if (stripSpaces)
		s = s.stripWhiteSpace();

	QStringList line = s.split(sep);
	int cols = line.size();

	int startRow = 1, startCol = 0;
	switch(importAs){
		case Matrix::Overwrite:
			if (d_cols != cols)
				setColumnCount(cols);
			if (d_rows != rows)
				setRowCount(rows);
		break;
		case Matrix::NewColumns:
			startCol = d_cols;
			setColumnCount(d_cols + cols);
			if (d_rows < rows)
				setRowCount(rows);
		break;
		case Matrix::NewRows:
			startRow = d_rows;
			if (d_cols < cols)
				setColumnCount(cols);
			setRowCount(d_rows + rows);
		break;
	}

	for (int j = startCol; j < d_cols; j++){
		int aux = j - startCol;
		if (cols > aux){
			if (updateDecimalSeparators)
				setCell(0, j, locale.toDouble(line[aux]));
			else
				setText(0, j, line[aux]);
		}
	}

	qApp->processEvents(QEventLoop::ExcludeUserInput);
	if (startCol){
		for (int i = startRow; i < d_rows; i++){
			s = t.readLine();
			if (simplifySpaces)
				s = s.simplifyWhiteSpace();
			else if (stripSpaces)
				s = s.stripWhiteSpace();
			line = s.split(sep);

			int nc = line.size() + startCol;
			if (nc > d_cols)
				setColumnCount(nc);

			for (int j = startCol; j < nc; j++){
				if (updateDecimalSeparators)
					setCell(i, j, locale.toDouble(line[j - startCol]));
				else
					setText(i, j, line[j - startCol]);
			}
		}
	} else {
		for (int i = startRow; i < d_rows; i++){
			s = t.readLine();
			if (simplifySpaces)
				s = s.simplifyWhiteSpace();
			else if (stripSpaces)
				s = s.stripWhiteSpace();
			line = s.split(sep);
			int lc = line.size();
			if (lc > d_cols)
				setColumnCount(lc);

			for (int j = 0; j < lc; j++){
				if (updateDecimalSeparators)
					setCell(i, j, locale.toDouble(line[j]));
				else
					setText(i, j, line[j]);
			}
		}
	}

	f.remove();//remove temp file
	if (d_matrix)
		d_matrix->resetView();

	d_calculated_values = false;

	QApplication::restoreOverrideCursor();
	return true;
}

void MatrixModel::setNumericFormat(char f, int prec)
{
	if (d_txt_format == f && d_num_precision == prec)
		return;

	d_txt_format = f;
	d_num_precision = prec;
}

void MatrixModel::transpose()
{
	int size = d_rows*d_cols;
	double *data = d_matrix->initWorkspace(size);
	if (!data)
		return;

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
	for(int i = 0; i < size; i++)
		data[i] = d_data[i];

	int old_cols = d_cols;
	d_cols = d_rows;
	d_rows = old_cols;
	int aux = 0;
	for(int i = 0; i < d_rows; i++){
		for(int j = 0; j < d_cols; j++)
			d_data[aux++] = data[j*old_cols + i];
	}
	d_matrix->freeWorkspace();
	QApplication::restoreOverrideCursor();
}

void MatrixModel::flipVertically()
{
	int size = d_rows*d_cols;
	double *data = d_matrix->initWorkspace(size);
	if (!data)
		return;

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
	for(int i = 0; i < size; i++)
		data[i] = d_data[i];

	int aux = 0;
	for(int i = 0; i < d_rows; i++){
		int row = (d_rows - i - 1)*d_cols;
		for(int j = 0; j < d_cols; j++)
			d_data[aux++] = data[row++];
	}
	d_matrix->freeWorkspace();
	QApplication::restoreOverrideCursor();
}

void MatrixModel::flipHorizontally()
{
	int size = d_rows*d_cols;
	double *data = d_matrix->initWorkspace(size);
	if (!data)
		return;

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
	for(int i = 0; i < size; i++)
		data[i] = d_data[i];

	int aux = 0;
	for(int i = 0; i < d_rows; i++){
		int row = i*d_cols;
		for(int j = d_cols - 1; j >= 0; j--)
			d_data[aux++] = data[row + j];
	}
	d_matrix->freeWorkspace();
	QApplication::restoreOverrideCursor();
}

void MatrixModel::rotate90(bool clockwise)
{
	int size = d_rows*d_cols;
	double *data = d_matrix->initWorkspace(size);
	if (!data)
		return;

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
	for(int i = 0; i < size; i++)
		data[i] = d_data[i];

	int old_rows = d_rows;
	int old_cols = d_cols;
	d_cols = d_rows;
	d_rows = old_cols;
	if (clockwise){
		int cell = 0;
		int aux = old_rows - 1;
		for(int i = 0; i < d_rows; i++){
			for(int j = 0; j < d_cols; j++)
				d_data[cell++] = data[(aux - j)*old_cols + i];
		}
	} else {
		int cell = 0;
		int aux = old_cols - 1;
		for(int i = 0; i < d_rows; i++){
			int k = aux - i;
			for(int j = 0; j < d_cols; j++)
				d_data[cell++] = data[j*old_cols + k];
		}
	}
	d_matrix->freeWorkspace();
	QApplication::restoreOverrideCursor();
}

bool MatrixModel::initWorkspace()
{
	gsl_set_error_handler_off();

	if (!d_direct_matrix)
		d_direct_matrix = gsl_matrix_alloc(d_rows, d_cols);
	if (!d_inv_matrix)
		d_inv_matrix = gsl_matrix_alloc(d_rows, d_cols);
	if (!d_inv_perm)
		d_inv_perm = gsl_permutation_alloc(d_cols);
	if (!d_direct_matrix || !d_inv_matrix || !d_inv_perm){
		QApplication::restoreOverrideCursor();
		QMessageBox::critical(d_matrix, tr("QtiPlot") + " - " + tr("Memory Allocation Error"),
		tr("Not enough memory, operation aborted!"));
		return false;
	}
	return true;
}

void MatrixModel::invert()
{
	initWorkspace();
	if(!d_direct_matrix || !d_inv_matrix || !d_inv_perm)
		return;

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

	int i, aux = 0;
	for(i=0; i<d_rows; i++){
		for(int j=0; j<d_cols; j++)
			gsl_matrix_set(d_direct_matrix, i, j, d_data[aux++]);
	}

	gsl_linalg_LU_decomp(d_direct_matrix, d_inv_perm, &i);
	gsl_linalg_LU_invert(d_direct_matrix, d_inv_perm, d_inv_matrix);

	gsl_matrix_free(d_direct_matrix);
	d_direct_matrix = NULL;
	gsl_permutation_free(d_inv_perm);
	d_inv_perm = NULL;

	aux = 0;
	for(int i=0; i<d_rows; i++){
		for(int j=0; j<d_cols; j++)
			d_data[aux++] = gsl_matrix_get(d_inv_matrix, i, j);
	}
	gsl_matrix_free(d_inv_matrix);
	d_inv_matrix = NULL;
	QApplication::restoreOverrideCursor();
}

void MatrixModel::clear(int startRow, int endRow, int startCol, int endCol)
{
	if (endRow < 0)
		endRow = d_rows - 1;
	if (endCol < 0)
		endCol = d_cols - 1;

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
	for (int i = startRow; i <= endRow; i++){
		int aux = i*d_cols + startCol;
		for (int j = startCol; j <= endCol; j++){
			d_data[aux++] = NAN;
		}
	}
	d_calculated_values = false;
	QApplication::restoreOverrideCursor();
}

double * MatrixModel::dataCopy(int startRow, int endRow, int startCol, int endCol)
{
	if (endRow < 0)
		endRow = d_rows - 1;
	if (endCol < 0)
		endCol = d_cols - 1;

	double *buffer = (double *)malloc((endRow - startRow + 1)*(endCol - startCol + 1) * sizeof (double));
	if (!buffer)
		return NULL;

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

	int aux = 0;
	for (int i = startRow; i <= endRow; i++){
		int row = i*d_cols + startCol;
		for (int j = startCol; j <= endCol; j++){
			buffer[aux++] = d_data[row++];
		}
	}

	QApplication::restoreOverrideCursor();
	return buffer;
}

bool MatrixModel::muParserCalculate(int startRow, int endRow, int startCol, int endCol)
{
	if (d_matrix->formula().count("\n") > 0){
		QString mess = tr("Multiline expressions take much more time to evaluate! Do you want to continue anyways?");
		if (QMessageBox::Yes != QMessageBox::warning(matrix(), tr("QtiPlot") + " - " + tr("Warning"), mess,
						   QMessageBox::Yes, QMessageBox::Cancel))
			return false;
	}

	muParserScript *mup = new muParserScript(d_matrix->scriptingEnv(), d_matrix->formula(),
											d_matrix, QString("<%1>").arg(d_matrix->objectName()));
	if (endRow < 0)
		endRow = d_rows - 1;
	if (endCol < 0)
		endCol = d_cols - 1;
	if (endCol >= d_cols)
		setColumnCount(endCol + 1);
	if (endRow >= d_rows)
		setRowCount(endRow + 1);

	double dx = d_matrix->dx();
	double dy = d_matrix->dy();
	double x_start = d_matrix->xStart();
	double y_start = d_matrix->yStart();

	double *ri = mup->defineVariable("i");
	double *rr = mup->defineVariable("row");
	double *cj = mup->defineVariable("j");
	double *cc = mup->defineVariable("col");
	double *x = mup->defineVariable("x");
	double *y = mup->defineVariable("y");

	double r = 1.0, c = 1.0;
	*ri = r; *rr = r;
	*y = y_start + dy;
	*cj = c; *cc = c;
	*x = x_start + dx;

	if (!mup->compile()){
		QApplication::restoreOverrideCursor();
		return false;
	}

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

	if (mup->codeLines() == 1){
		for(int row = startRow; row <= endRow; row++){
			double r = row + 1.0;
			*ri = r; *rr = r;
			*y = y_start + row*dy;
			int aux = row*d_cols + startCol;
			for(int col = startCol; col <= endCol; col++){
				double c = col + 1.0;
				*cj = c; *cc = c;
				*x = x_start + col*dx;
				d_data[aux++] = mup->evalSingleLine();
			}
		}
	} else {
		QVariant res;
		for(int row = startRow; row <= endRow; row++){
			double r = row + 1.0;
			*ri = r; *rr = r;
			*y = y_start + row*dy;
			int aux = row*d_cols + startCol;
			for(int col = startCol; col <= endCol; col++){
				double c = col + 1.0;
				*cj = c; *cc = c;
				*x = x_start + col*dx;
				res = mup->eval();
				if (res.canConvert(QVariant::Double))
					 d_data[aux++] = res.toDouble();
				else
					d_data[aux++] = NAN;
				qApp->processEvents();
			}
		}
	}

	d_calculated_values = (fabs(endRow - startRow) + 1 == d_rows && fabs(endCol - startCol) + 1 == d_cols);

	QApplication::restoreOverrideCursor();
	return true;
}

bool MatrixModel::calculate(int startRow, int endRow, int startCol, int endCol)
{
	QString formula = d_matrix->formula();
	if (formula.isEmpty())
		return false;

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

	ScriptingEnv *scriptEnv = d_matrix->scriptingEnv();
	Script *script = scriptEnv->newScript(formula, d_matrix, QString("<%1>").arg(objectName()));
	connect(script, SIGNAL(error(const QString&, const QString&, int)), scriptEnv, SIGNAL(error(const QString&, const QString&, int)));
	connect(script, SIGNAL(print(const QString&)), scriptEnv, SIGNAL(print(const QString&)));

	if (!script->compile()){
		QApplication::restoreOverrideCursor();
		return false;
	}

	if (endRow < 0)
		endRow = d_rows - 1;
	if (endCol < 0)
		endCol = d_cols - 1;
	if (endCol >= d_cols)
		setColumnCount(endCol + 1);
	if (endRow >= d_rows)
		setRowCount(endRow + 1);

	QVariant res;
	double dx = d_matrix->dx();
	double dy = d_matrix->dy();
	double x_start = d_matrix->xStart();
	double y_start = d_matrix->yStart();
	double r = 0.0, c = 0.0;
	for(int row = startRow; row <= endRow; row++){
	    r = row + 1.0;
		script->setDouble(r, "i");
		script->setDouble(r, "row");
		script->setDouble(y_start + row*dy, "y");
		int aux = row*d_cols + startCol;
		for(int col = startCol; col <= endCol; col++){
		    c = col + 1.0;
			script->setDouble(c, "j");
			script->setDouble(c, "col");
			script->setDouble(x_start + col*dx, "x");
			res = script->eval();

			if (res.canConvert(QVariant::Double))
				d_data[aux++] = res.toDouble();
			else {
				QApplication::restoreOverrideCursor();
				d_data[aux++] = NAN;
				return false;
			}
		}
		qApp->processEvents();
	}

	d_calculated_values = (fabs(endRow - startRow) + 1 == d_rows && fabs(endCol - startCol) + 1 == d_cols);

	QApplication::restoreOverrideCursor();
	return true;
}

void MatrixModel::fft(bool inverse)
{
	int width = d_cols;
	int height = d_rows;

	double **x_int_re = Matrix::allocateMatrixData(height, width); /* real coeff matrix */
	if (!x_int_re)
		return;
	double **x_int_im = Matrix::allocateMatrixData(height, width); /* imaginary coeff  matrix*/
	if (!x_int_im){
		Matrix::freeMatrixData(x_int_re, height);
		return;
	}

	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

	int cell = 0;
	for (int i = 0; i < height; i++){
		for (int j = 0; j < width; j++){
			x_int_re[i][j] = d_data[cell++];
			x_int_im[i][j] = 0.0;
		}
	}

	if (inverse){
		double **x_fin_re = Matrix::allocateMatrixData(height, width);
		double **x_fin_im = Matrix::allocateMatrixData(height, width);
		if (!x_fin_re || !x_fin_im){
			Matrix::freeMatrixData(x_int_re, height);
			Matrix::freeMatrixData(x_int_im, height);
			QApplication::restoreOverrideCursor();
			return;
		}

		fft2d_inv(x_int_re, x_int_im, x_fin_re, x_fin_im, width, height);
		cell = 0;
		for (int i = 0; i < height; i++){
			for (int j = 0; j < width; j++){
				double re = x_fin_re[i][j];
				double im = x_fin_im[i][j];
				d_data[cell++] = sqrt(re*re + im*im);
			}
		}
		Matrix::freeMatrixData(x_fin_re, height);
		Matrix::freeMatrixData(x_fin_im, height);
	} else {
		fft2d(x_int_re, x_int_im, width, height);
		cell = 0;
		for (int i = 0; i < height; i++){
			for (int j = 0; j < width; j++){
				double re = x_int_re[i][j];
				double im = x_int_im[i][j];
				d_data[cell++] = sqrt(re*re + im*im);
			}
		}
	}
	Matrix::freeMatrixData(x_int_re, height);
	Matrix::freeMatrixData(x_int_im, height);

	d_matrix->resetView();
	d_calculated_values = false;
	QApplication::restoreOverrideCursor();
}

void MatrixModel::pasteData(double *clipboardBuffer, int topRow, int leftCol, int rows, int cols)
{
	int newCols = leftCol + cols;
	if (newCols > d_cols)
		insertColumns(d_cols, newCols - d_cols);

	int newRows = topRow + rows;
	if (newRows > d_rows)
		insertRows(d_rows, newRows - d_rows);

	int cell = 0;
	int bottomRow = newRows - 1;
	int rightCol = newCols - 1;
	for (int i = topRow; i <= bottomRow; i++){
		int row = i*d_cols + leftCol;
		for (int j = leftCol; j <= rightCol; j++)
			d_data[row++] = clipboardBuffer[cell++];
	}

	d_calculated_values = false;
}

void MatrixModel::resample(int rows, int cols, int method)
{
#ifdef HAVE_ALGLIB
	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

	ap::real_2d_array oldValues, newValues;
	oldValues.setlength(d_rows, d_cols);
	newValues.setlength(rows, cols);

	for (int i = 0; i < d_rows; i++){
		for (int j = 0; j < d_cols; j++)
			oldValues(i, j) = cell(i, j);
	}

	if (method == Matrix::Bilinear)
		spline2dresamplebilinear(oldValues, d_rows, d_cols, newValues, rows, cols);
	else
		spline2dresamplebicubic(oldValues, d_rows, d_cols, newValues, rows, cols);

	setDimensions(rows, cols);

	for (int i = 0; i < rows; i++){
		for (int j = 0; j < cols; j++)
			setCell(i, j, newValues(i, j));
	}
	d_matrix->resetView();

	QApplication::restoreOverrideCursor();
#endif
}
